package net.dromard.common.jfreechart;

import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileOutputStream;
import java.text.NumberFormat;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.SpiderWebPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RectangleEdge;

import com.keypoint.PngEncoder;

/**
 * Spider chart.
 * Here is an example of a spider chart generated by this class:
 * <br/>
 * <img src="doc-files/spiderChart.png">
 * <br>
 * @author Gabriel Dromard       
 */
public class SpiderChart {
    /**
     * The serialVersionUID is a universal version identifier for a Serializable class. Deserialization uses this number to ensure that a loaded class
     * corresponds exactly to a serialized object. If no match is found, then an InvalidClassException is thrown.
     * @see Serializable
     */
    private static final long serialVersionUID = -7843338167512214824L;

    /** Makes the category name fill in the image when generating small chart. */
    private static final double INTERIOR_GAP = 0.40000000000000002D;
    /** Axis Label Gap. */
    private static final double AXIS_LABEL_GAP = 0.02D;

    /**
     * Spider chart category dataset.
     */
    private DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
    /** The max value of the spider chart. */
    private int maxValue = -1;
    /**
     * Spider Chart constructor.
     * @param maxValue The max value of the spider chart
     */
    public SpiderChart(final int maxValue) {
        this.maxValue = maxValue;
    }

    /**
     * Add a spide to chart.
     * @param chartName  The chart name
     * @param categories The categories
     * @param values     The values (per category)
     */
    public final void add(final String chartName, final String[] categories, final double[] values) {
        if (categories.length != values.length) {
            throw new IndexOutOfBoundsException("Number of values invalid (must fit to the number of categories)");
        }
        for (int i = 0; i < values.length; i++) {
            categoryDataset.addValue(values[i], chartName, categories[i]);
        }
    }

    /**
     * Create the JFreeChart.
     * @return The JFreeChart instance corresponding to the wanted spider chart
     */
    private JFreeChart createSpiderChart() {
        plot.setDataset(categoryDataset);
        SpiderWebPlot spiderwebplot = plot;
        //SpiderWebPlot spiderwebplot = new SpiderWebPlot(categoryDataset);
        spiderwebplot.setMaxValue(maxValue);
        spiderwebplot.setInteriorGap(INTERIOR_GAP);
        spiderwebplot.setAxisLabelGap(AXIS_LABEL_GAP);
        JFreeChart jfreechart = new JFreeChart(null, TextTitle.DEFAULT_FONT, spiderwebplot, false);
        LegendTitle legendtitle = new LegendTitle(spiderwebplot);
        legendtitle.setPosition(RectangleEdge.BOTTOM);
        jfreechart.addSubtitle(legendtitle);
        return jfreechart;
    }

    /**
     * Retreive the spider chart as image.
     * @param width  The image width
     * @param height The image height
     * @return The spider char as a buffered image
     */
    public final Image getImage(final int width, final int height) {
        return createSpiderChart().createBufferedImage(width, height);
    }

    /**
     *
     * @param args
     * @throws Exception
     */
    public static void main(final String[] args) throws Exception {
        SpiderChart chart = new SpiderChart(6);
        String[] categories = new String[] {"Category 1", "Category 2", "Category 3"};
        double[] values = new double[] {4D, 3D, 2D};

        chart.add("Radar", categories, values);
        values = new double[] {3D, 3D, 3D};
        chart.add("Neutral", categories, values);

        Image image = chart.getImage(300, 300);
        PngEncoder encoder = new PngEncoder(image, false, PngEncoder.FILTER_NONE, 0);
        byte[] data = encoder.pngEncode();
        File png = new File("spiderChart.png");
        if (!png.exists()) {
            png.createNewFile();
        }
        FileOutputStream os = new FileOutputStream(png);
        os.write(data);
        os.close();
        System.out.println("Chart generated to " + png.getAbsolutePath());

        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(300, 300);
        JLabel l = new JLabel();
        l.setIcon(new ImageIcon(image));
        f.getContentPane().add(l);
        f.setVisible(true);
    }

    /** Fixed spider plot implementation so as to handle graduation. */
    private final SpiderWebPlot plot = new SpiderWebPlot() {
        /**
         * The serialVersionUID is a universal version identifier for a Serializable class. Deserialization uses this number to ensure that a loaded class
         * corresponds exactly to a serialized object. If no match is found, then an InvalidClassException is thrown.
         * @see Serializable
         */
        private static final long serialVersionUID = -728871171343227632L;
        //put this many labels on each axis.
        private int ticks = DEFAULT_TICKS;
        private static final int DEFAULT_TICKS = 5;
        private NumberFormat format = NumberFormat.getInstance();
        //constant for creating perpendicular tick marks.
        private static final double PERPENDICULAR = 90;
        //the size of a tick mark, as a percentage of the entire line length.
        private static final double TICK_SCALE = 0.015;
        //the gap between the axis line and the numeric label itself.
        private int valueLabelGap = DEFAULT_GAP;
        private static final int DEFAULT_GAP = 10;
        //the threshold used for determining if something is "on" the axis
        private static final double THRESHOLD = 15;

        /**
         * {@inheritDoc}
         */
        protected void drawLabel(final Graphics2D g2, final  Rectangle2D plotArea, final double value,
                                 final int cat, final double startAngle, final double extent) {
            super.drawLabel(g2, plotArea, value, cat, startAngle, extent);
            final FontRenderContext frc = g2.getFontRenderContext();
            final double[] transformed = new double[2];
            final double[] transformer = new double[2];
            final Arc2D arc1 = new Arc2D.Double(plotArea, startAngle, 0, Arc2D.OPEN);

            if (getMaxValue() > 0) {
                ticks = (int) getMaxValue();
            }

            for (int i = 1; i <= ticks; i++) {

                final Point2D point1 = arc1.getEndPoint();

                final double deltaX = plotArea.getCenterX();
                final double deltaY = plotArea.getCenterY();
                double labelX = point1.getX() - deltaX;
                double labelY = point1.getY() - deltaY;

                final double scale = ((double) i / (double) ticks);
                final AffineTransform tx = AffineTransform.getScaleInstance(scale, scale);
                //for getting the tick mark start points.
                final AffineTransform pointTrans = AffineTransform.getScaleInstance(scale + TICK_SCALE, scale + TICK_SCALE);
                transformer[0] = labelX;
                transformer[1] = labelY;
                pointTrans.transform(transformer, 0, transformed, 0, 1);
                final double pointX = transformed[0] + deltaX;
                final double pointY = transformed[1] + deltaY;
                tx.transform(transformer, 0, transformed, 0, 1);
                labelX = transformed[0] + deltaX;
                labelY = transformed[1] + deltaY;



                double rotated = (PERPENDICULAR);

                AffineTransform rotateTrans = AffineTransform.getRotateInstance(Math.toRadians(rotated), labelX, labelY);
                transformer[0] = pointX;
                transformer[1] = pointY;
                rotateTrans.transform(transformer, 0, transformed, 0, 1);
                final double x1 = transformed[0];
                final double y1 = transformed[1];

                rotated = (-PERPENDICULAR);
                rotateTrans = AffineTransform.getRotateInstance(Math.toRadians(rotated), labelX, labelY);

                rotateTrans.transform(transformer, 0, transformed, 0, 1);

                final Composite saveComposite = g2.getComposite();
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));

                g2.draw(new Line2D.Double(transformed[0], transformed[1], x1, y1));
                /* Uncomment this if you want to print graduation values:
                if (startAngle == this.getStartAngle()) {
                    final String label = format.format(((double) i / (double) ticks) * this.getMaxValue());
                    //final Rectangle2D labelBounds = getLabelFont().getStringBounds(label, frc);

                    final LineMetrics lm = getLabelFont().getLineMetrics(label, frc);
                    final double ascent = lm.getAscent();

                    // move based on quadrant.
                    if (Math.abs(labelX - plotArea.getCenterX()) < THRESHOLD) {
                        // on Y Axis, label to right.
                        labelX += valueLabelGap;
                        // center vertically.
                        labelY += ascent / (float) 2;
                    } else if (Math.abs(labelY - plotArea.getCenterY()) < THRESHOLD) {
                        // on X Axis, label underneath.
                        labelY += valueLabelGap;
                    } else if (labelX >= plotArea.getCenterX()) {
                        if (labelY < plotArea.getCenterY()) {
                            // quadrant 1
                            labelX += valueLabelGap;
                            labelY += valueLabelGap;
                        } else {
                            // quadrant 2
                            labelX -= valueLabelGap;
                            labelY += valueLabelGap;
                        }
                    } else {
                        if (labelY > plotArea.getCenterY()) {
                            // quadrant 3
                            labelX -= valueLabelGap;
                            labelY -= valueLabelGap;
                        } else {
                            // quadrant 4
                            labelX += valueLabelGap;
                            labelY -= valueLabelGap;
                        }
                    }
                    g2.setPaint(getLabelPaint());
                    g2.setFont(getLabelFont());
                    //g2.drawString(label, (float) labelX, (float) labelY);
                }
                */
                g2.setComposite(saveComposite);
            }
        }

        /**
         * Sets the number of tick marks on this spider chart.
         * @param ticks the new number of tickmarks.
        public void setTicks(final int ticks) {
            this.ticks = ticks;
        }
         */

        /**
         * sets the numberformat for the tick labels on this spider chart.
         * @param format the new number format object.
        public void setFormat(final NumberFormat format) {
            this.format = format;
        }
         */
    };
}
